User-Controlled Sprite
There are several ways to get input from a user. He can use the mouse, they keyboard, or the joystick. This method focuses on user keyboard input to control a sprite. We can trap keyboard events in graphics with "when characterInput" and then checking the value of Inkey$. The method used in this demo is different and it relies on the state of keyboard keys, which are checked in a loop.
What is a Virtual Key?
When we talk about ASCII text, there is a difference between uppercase and lowercase letters. 'A' is not the same as 'a'. The computer keyboard does not make that distinction. It only knows which keys are down. There is only one 'A' key on the keyboard!
Checking the State of a Key
There is no easy way to discover which keys are down at a specified time. We have to query each key that interests us! We do that with the function GetAsyncKeyState.
CallDLL #user32,"GetAsyncKeyState",_VK_WHATEVER as long, keyDown as long
The first argument for the function is the "virtual key code" for the key to be checked. The function returns 0 if the key isn't down, less than zero if it is down when the call is made.
Virtual Key Codes
Each keyboard key has a Virtual Key Code. The code is a Windows constant and these are expressed in LB by preceding them with an underscore character. The VK code for the 'A' key is
_VK_A
Many of these key codes are documented in the LB Helpfile under "Using virtual key constants with Inkey$." The VK code for the left arrow key is
_VK_LEFT
The Minimum Loop
There are two commands that must be included in any continuous loop in Liberty BASIC. The first is SCAN. SCAN checks for user events. It is extremely important to use SCAN. If SCAN is not used, the program cannot recognize things like button presses, menu clicks, or user attempts to close a window. When the program is stuck in a continuous loop, the user must press the Control key and the Break(Pause) key to halt program execution.
The second command isn't essential, but it is advisable to use it. When the program enters a continous loop, all processor cycles are dedicated to this loop. To reduce processor load, use the "Sleep" command from kernel32.dll. See issue #105 for Dennis McKinney's article on this method. [http://babek.info/libertybasicfiles/lbnews/nl105/MinimizeCPU.htm]
Here is the minimum loop:
[mainLoop] 'the entire game runs from this loop
SCAN 'check for user events such as window closing
'reduce processor use and introduce delay with Sleep command:
calldll #kernel32, "Sleep",100 as long, re as long
goto [mainLoop]
The value "100" in the code above means that the program halts for 100 milliseconds. Use smaller or larger values for this argument as needed by your program.
Checking a Key's State
If we place a command to GetAsyncKeyState inside the loop, we can monitor user keypresses for that key. Here is the minimum loop with the addition of a check for the state of the left arrow key:
[mainLoop] 'the entire game runs from this loop
SCAN 'check for user events such as window closing
CallDLL #user32,"GetAsyncKeyState",_VK_LEFT as long, keyDown as long
if keyDown<0 then gosub [goLeft]
'reduce processor use and introduce delay with Sleep command:
calldll #kernel32, "Sleep",100 as long, re as long
goto [mainLoop]
The call checks for the state of the _VK_LEFT key. If the return from the function is less than zero, the program goes to the subroutine that moves the sprite to the left. IMPORTANT! When you use a continuous loop, it is essential to return the program to this loop after actions are performed. The easiest way to insure that the loop is reactivated is to leave it ONLY with a GOSUB command, which returns program execution to the loop when the subroutine ends. If you use GOTO to jump to a routine in a different part of that program, the other routine MUST end with a
goto [mainLoop]
Of course, called SUBs and FUNCTIONs can also be called from within the [mainLoop] with no adverse consequences.
Checking Four Directions
It is best to allow your game's user to use either the arrow keys or the numeric keypad, because some setups have one of these available and not the other. Here is a game loop which checks for the directions up, down, left and right.
[mainLoop] 'the entire game runs from this loop
SCAN 'check for user events such as window closing
CallDLL #user32,"GetAsyncKeyState",_VK_RIGHT as long, keyDown as long
if keyDown<0 then gosub [goRight]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD6 as long, keyDown as long
if keyDown<0 then gosub [goRight]
CallDLL #user32,"GetAsyncKeyState",_VK_LEFT as long, keyDown as long
if keyDown<0 then gosub [goLeft]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD4 as long, keyDown as long
if keyDown<0 then gosub [goLeft]
CallDLL #user32,"GetAsyncKeyState",_VK_UP as long, keyDown as long
if keyDown<0 then gosub [goUp]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD8 as long, keyDown as long
if keyDown<0 then gosub [goUp]
CallDLL #user32,"GetAsyncKeyState",_VK_DOWN as long, keyDown as long
if keyDown<0 then gosub [goDown]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD5 as long, keyDown as long
if keyDown<0 then gosub [goDown]
'reduce processor use and introduce delay with Sleep command:
calldll #kernel32, "Sleep",100 as long, re as long
goto [mainLoop]
Now Move!
If an arrow key is pressed to send the sprite in a particular direction, the program must change the coordinates of the sprite to reflect the new location. It is best to keep the coordinates in variables, and incrememnt or decrement these variables as needed. If the user moves the sprite to the left, the X coordinate is decremented by a small number to cause the sprite to move in that direction. A value of 5 or 10 is good for the decrememnt. This example stores the X location in the variable guyX and decrements it by 5 when the sprite moves to the left.
guyX = guyX - 5
#1 "spriteorient guy mirror" 'guy faces left now
gosub [moveGuy]
Facing the Correct Direction
If your sprite faces one direction or another, you'll want to use the "flip" and "mirror" options in the "spriteorient" command. If you don't do this, the sprite may appear to be walking, driving, or flying backwards! This code orients the sprite so that it is facing left when the user hits the left arrow key or the numberpad 4 key.
#1 "spriteorient guy mirror" 'guy faces left now
Don't Fall Off the Edge!
You probably want to keep your sprite on screen, so that it doesn't disappear after it reaches one of the edges. Do this by checking the location, either with the "spritexy?" command or by using variables to locate the sprite, and checking the values of these variables. When the sprite gets to the 0 location in the X direction, it can be stopped from moving further to the left. When the sprite gets to the maximum X location in the X direction, it can be stopped from moving further to the right. You'll need to know the width and height of your graphics area to insure that the sprite doesn't go beyond them.
#1 "spritexy? guy x y"
if x <= 0 then return 'guy is at left side already
Hey, Where is My Animation!
Don't forget that the screen is only updated when you issue a "drawsprites" command. The demo below calls this routine whenever the user hits one of the four directional keys. It locates the sprite in a new spot and updates the display. The demo program stores the coordinates of the sprite in variables called "guyX" and "guyY."
[moveGuy]
'put sprite in new location
#1 "spritexy guy ";guyX;" ";guyY
#1 "drawsprites" 'update screen!
RETURN
DEMO
Here is a small demo program that uses a background bitmap and two sprite bitmaps that come with the Liberty BASIC distribution. Copy it and save it to disk in the directory that contains your copy of Liberty BASIC.
'save and run in the LB root directory
'requires LB4 for caveman sprites
nomainwin
maxX=550 : maxY=250
WindowWidth=maxX+50 : WindowHeight=maxY+50
loadbmp "bkg","SPRITES\BG1.bmp"
loadbmp "guy1","SPRITES\cave1.bmp"
loadbmp "guy2","SPRITES\cave2.bmp"
open "Move This Sprite" for graphics_nsb_nf as #1
#1 "trapclose [quit]"
'set the background
#1 "background bkg"
'add your sprite
#1 "addsprite guy guy1 guy2"
#1 "cyclesprite guy 1"
'set sprite location
guyX=100 : guyY=150
#1 "spritexy guy ";guyX;" ";guyY
'show frame onscreen
#1 "drawsprites"
[mainLoop] 'the entire game runs from this loop
SCAN 'check for user events such as window closing
CallDLL #user32,"GetAsyncKeyState",_VK_RIGHT as long, keyDown as long
if keyDown<0 then gosub [goRight]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD6 as long, keyDown as long
if keyDown<0 then gosub [goRight]
CallDLL #user32,"GetAsyncKeyState",_VK_LEFT as long, keyDown as long
if keyDown<0 then gosub [goLeft]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD4 as long, keyDown as long
if keyDown<0 then gosub [goLeft]
CallDLL #user32,"GetAsyncKeyState",_VK_UP as long, keyDown as long
if keyDown<0 then gosub [goUp]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD8 as long, keyDown as long
if keyDown<0 then gosub [goUp]
CallDLL #user32,"GetAsyncKeyState",_VK_DOWN as long, keyDown as long
if keyDown<0 then gosub [goDown]
CallDLL #user32,"GetAsyncKeyState",_VK_NUMPAD5 as long, keyDown as long
if keyDown<0 then gosub [goDown]
'reduce processor use and introduce delay with Sleep command:
calldll #kernel32, "Sleep",100 as long, re as long
goto [mainLoop]
[goLeft]
#1 "spritexy? guy x y"
if x <= 0 then return 'guy is at left side already
guyX = guyX - 5
#1 "spriteorient guy mirror" 'guy faces left now
gosub [moveGuy]
return
[goRight]
#1 "spritexy? guy x y"
if x >= maxX then return 'guy is at right side already
guyX = guyX + 5
#1 "spriteorient guy normal" 'guy faces right now
gosub [moveGuy]
return
[goUp]
#1 "spritexy? guy x y"
if y <= 0 then return 'guy is at top already
guyY = guyY - 5
gosub [moveGuy]
return
[goDown]
#1 "spritexy? guy x y"
if y >= maxY then return 'guy is at bottom already
guyY = guyY + 5
gosub [moveGuy]
return
[moveGuy]
'put sprite in new location
#1 "spritexy guy ";guyX;" ";guyY
#1 "drawsprites" 'update screen!
RETURN
[quit]
unloadbmp "bkg"
unloadbmp "guy1"
unloadbmp "guy2"
close #1:end